
#include <QFile>
#include <QFileInfo>
#include <QFileDialog>
#include <QRegExp>
#include <QRegularExpression>
#include <QDebug>

#include <zlib.h>

#define LIBDPKG_VOLATILE_API
#include <dpkg/dpkg.h>
#include <dpkg/pkg-array.h>
#include <dpkg/pkg-show.h>

#include "mainwindow.h"
#include "ui_mainwindow.h"

PKGInfo::PKGInfo(const QString &name, const QString &version, const QString &desc, PKGInfo::Status status, const QStringList &cves, QObject *parent) : QObject{parent}
{
    this->name = name;
    this->version = version;
    this->desc = desc;
    this->cves = cves;
    this->status = status;
    if (!this->name.isEmpty()) {
        QStringList changelog_filenames = { "changelog.gz", "changelog.Debian.gz" };
        for (const QString &name : qAsConst(changelog_filenames)) {
            changelogfile = QString("/usr/share/doc/%1/%2").arg(this->name, name);
            if (QFileInfo::exists(changelogfile)) {
                break;
            } else {
                changelogfile.clear();
            }
        }
    }
}
PKGInfo::PKGInfo(const PKGInfo &info, QObject *parent) : QObject{parent} { copy(info); }
PKGInfo &PKGInfo::operator=(const PKGInfo &info) { return copy(info); }
PKGInfo &PKGInfo::copy(const PKGInfo &info)
{
    this->name = info.name;
    this->version = info.version;
    this->desc = info.desc;
    this->cves = info.cves;
    this->status = info.status;
    this->changelogfile = info.changelogfile;
    return *this;
}

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    this->setWindowIcon(QIcon("://icons/cve.svg"));

    ui_max_widht_name = 0;
    ui_max_widht_version = 0;

    m_model_combo = new QStandardItemModel(this);
    m_model_table = new QStandardItemModel(this);

    m_completer = new QCompleter(this);
    m_completer->setCaseSensitivity(Qt::CaseInsensitive);
    m_completer->setFilterMode(Qt::MatchContains);
    m_completer->setCompletionMode(QCompleter::PopupCompletion);
    m_completer->setModel(m_model_combo);

    ui->comboBox_package->setModel(m_model_combo);
    ui->comboBox_package->setCompleter(m_completer);
    ui->tableView->setModel(m_model_table);

    ui->checkBox_onlycve->setChecked(true);
    ui->comboBox_package->setCurrentIndex(-1);

    UpdatePackages();
    UpdateUiDatas(ui->checkBox_onlycve->isChecked());

    connect(ui->checkBox_onlycve, SIGNAL(toggled(bool)), this, SLOT(On_OnlyShowCVE_Toggled(bool)));
    connect(ui->comboBox_package, SIGNAL(currentIndexChanged(int)), this, SLOT(On_ComboboxIndex_Changed(int)));
    connect(ui->tableView, SIGNAL(clicked(QModelIndex)), this, SLOT(On_Table_Clicked(QModelIndex)));
    connect(ui->tableView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(On_Table_DoubleClicked(QModelIndex)));
}

MainWindow::~MainWindow()
{
    delete ui;
}

const QByteArray MainWindow::GZipUncompress(const QByteArray &src)
{
    QByteArray buffer_out;
    z_stream strm;
    strm.zalloc = NULL;
    strm.zfree = NULL;
    strm.opaque = NULL;

    strm.avail_in = src.size();
    strm.next_in = (Bytef *)src.data();

    int err = -1;
    err = inflateInit2(&strm, MAX_WBITS + 16);
    if (err == Z_OK) {
        while (true)
        {
            char buffer[4096] = { 0 };
            strm.avail_out = 4096;
            strm.next_out = (Bytef *)buffer;
            int code = inflate(&strm, Z_FINISH);
            buffer_out.append(buffer, 4096 - strm.avail_out);
            // qDebug()<<"ddd"<<code<<Z_OK;
            if (Z_STREAM_END == code ) {
                break;
            }
        }
    }
    inflateEnd(&strm);
    return buffer_out;
}

const QByteArray MainWindow::LoadChangelog(const PKGInfo &package)
{
    if (package.name.isEmpty()) {
        qWarning().noquote() << "Package name is empty!";
        return QByteArray();
    }
    if (package.changelogfile.isEmpty()) {
        qWarning().noquote() << QString("Package [%1] changelog file not exist!").arg(package.name);
        return QByteArray();
    }
    // qDebug().noquote() << "Package:" << package.name;
    // qDebug().noquote() << "Changelog File:" << package.changelogfile;

    QFile file(package.changelogfile);
    file.open(QIODevice::ReadOnly);
    QByteArray contents = file.readAll();
    file.close();

    return GZipUncompress(contents);
}

const QStringList MainWindow::GetPackageCVEs(const PKGInfo &package)
{
    QString contents = LoadChangelog(package);
    QStringList cves;

    m_re.setPattern("(CVE-\\d{4}-\\d{1,})");
    m_re.setPatternOptions(QRegularExpression::MultilineOption);
    if (m_re.isValid()) {
        QRegularExpressionMatchIterator iter = m_re.globalMatch(contents);
        while (iter.hasNext()) {
            QRegularExpressionMatch match = iter.next();
            QString cap = match.captured(1);
            if (!cves.contains(cap)) {
                cves.append(cap);
            }
        }
        // qDebug().noquote() << QString("CVE Count: %1").arg(cves.count());
        // qDebug().noquote() << QString("CVEs:\n %1").arg(cves.join("\n "));
    }
    return cves;
}

PKGInfo::Status ConvertStatus(pkgstatus status)
{
    switch (status) {
    case PKG_STAT_NOTINSTALLED:     return PKGInfo::Status::NOTINSTALLED;
    case PKG_STAT_CONFIGFILES:      return PKGInfo::Status::CONFIGFILES;
    case PKG_STAT_HALFINSTALLED:    return PKGInfo::Status::HALFINSTALLED;
    case PKG_STAT_UNPACKED:         return PKGInfo::Status::UNPACKED;
    case PKG_STAT_HALFCONFIGURED:   return PKGInfo::Status::HALFCONFIGURED;
    case PKG_STAT_TRIGGERSAWAITED:  return PKGInfo::Status::TRIGGERSAWAITED;
    case PKG_STAT_TRIGGERSPENDING:  return PKGInfo::Status::TRIGGERSPENDING;
    case PKG_STAT_INSTALLED:        return PKGInfo::Status::INSTALLED;
    }
    return static_cast<PKGInfo::Status>(status);
}

void MainWindow::UpdatePackages()
{
    struct pkg_array array;
    struct pkginfo *pkg = nullptr;
    struct pkgbin *pkgbin = nullptr;

    dpkg_set_report_piped_mode(_IOFBF);
    // dpkg_locales_init(PACKAGE);
    dpkg_program_init("dpkg-query");
    modstatdb_open(msdbrw_readonly);
    pkg_array_init_from_hash(&array);
    pkg_array_sort(&array, pkg_sorter_by_nonambig_name_arch);

    m_packages.clear();
    for (int i = 0; i < array.n_pkgs; i++) {
        pkg = array.pkgs[i];
        if (pkg->status == PKG_STAT_INSTALLED) {
            pkgbin = &pkg->installed;
            QString pkgname = QLatin1String(pkg->set->name);
            QString pkgdesc = QLatin1String(pkgbin->description);
            QString pkgver = QLatin1String(pkgbin->version.version);
            if (pkgbin->version.epoch > 0) {
                pkgver = QString("%2:%1").arg(pkgver, QString::number(pkgbin->version.epoch));
            }
            if (pkgbin->version.revision) {
                pkgver = QString("%1-%2").arg(pkgver, QLatin1String(pkgbin->version.revision));
            }
            PKGInfo info(pkgname, pkgver, pkgdesc, ConvertStatus(pkg->status));
            info.cves = GetPackageCVEs(info);
            m_packages.append(info);
        }
    }
    qDebug().noquote() << "Packages Count:" << m_packages.count();
}

void MainWindow::UpdateUiDatas(bool onlycve)
{
    QLabel label;
    int row = 0;
    int packages_count = 0;

    m_model_combo->clear();
    m_model_table->clear();

    m_model_table->setColumnCount(3);
    m_model_table->setHorizontalHeaderItem(0, new QStandardItem(tr("Package")));
    m_model_table->setHorizontalHeaderItem(1, new QStandardItem(tr("Version")));
    m_model_table->setHorizontalHeaderItem(2, new QStandardItem(tr("Description")));
    ui->tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
    ui->tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed);
    ui->tableView->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch);

    for (int i=0; i<m_packages.count(); i++) {
        const PKGInfo &info = m_packages.at(i);
        if (onlycve && info.cves.isEmpty()) {
            // qDebug().noquote() << QString("Package %1 has no CVE patch").arg(info.name);
            continue;
        }
        packages_count++;
        row = packages_count - 1;
        m_model_combo->setRowCount(packages_count);
        m_model_table->setRowCount(packages_count);

        // qDebug().noquote() << "Add Package:" << info.name;
        QStandardItem *combo_item = new QStandardItem(info.name);
        combo_item->setData(QVariant::fromValue(info));

        QStandardItem *table_name = new QStandardItem(info.name);
        table_name->setData(QVariant::fromValue(info));

        m_model_combo->setItem(row, 0, combo_item);
        m_model_table->setItem(row, 0, table_name);
        m_model_table->setItem(row, 1, new QStandardItem(info.version));
        m_model_table->setItem(row, 2, new QStandardItem(info.desc));

        label.setText(info.name);
        ui_max_widht_name = qMax(label.minimumSizeHint().width(), ui_max_widht_name);
        label.setText(info.version);
        ui_max_widht_version = qMax(label.minimumSizeHint().width(), ui_max_widht_version);

        ui->comboBox_package->resize(ui_max_widht_name, -1);
        ui->tableView->horizontalHeader()->resizeSection(0, ui_max_widht_name);
        ui->tableView->horizontalHeader()->resizeSection(1, ui_max_widht_version);
    }

    // Restore Combobox Index
    if (ui->comboBox_package->currentIndex() != -1) {
        for (int i=0; i<m_model_combo->rowCount(); i++) {
            if (m_model_combo->item(i, 0)->text() == m_combo_current_text) {
                ui->comboBox_package->setCurrentIndex(i);
            }
        }
    }
}

void MainWindow::On_OnlyShowCVE_Toggled(bool checked)
{
    UpdateUiDatas(checked);
}

void MainWindow::On_ComboboxIndex_Changed(int index)
{
    Q_UNUSED(index)

    m_combo_current_text = ui->comboBox_package->currentText();

    const PKGInfo &info = m_model_combo->item(index)->data().value<PKGInfo>();
    qDebug().noquote() << "Current Package:" << info.name << "CVE Count:" << info.cves.count();

    for (int row=0; row<m_model_table->rowCount(); row++) {
        if (m_model_table->item(row, 0)->text() == info.name) {
            ui->tableView->selectRow(row);
            break;
        }
    }

    ui->textBrowser->setText(info.cves.join("\n"));
}

void MainWindow::On_Table_Clicked(const QModelIndex &index)
{
    ui->comboBox_package->setCurrentIndex(index.row());

    QString name = m_model_table->item(index.row(), 0)->text();
    for (const PKGInfo &info : qAsConst(m_packages)) {
        if (info.name == name) {
            ui->textBrowser->setText(info.cves.join("\n"));
            // qDebug().noquote() << "Clicked row:" << index.row() << "Package:" << info.name << "CVE Count:" << info.cves.count();
            return;
        }
    }
}

void MainWindow::On_Table_DoubleClicked(const QModelIndex &index)
{
    ui->comboBox_package->setCurrentIndex(index.row());

    QString name = m_model_table->item(index.row(), 0)->text();
    for (const PKGInfo &info : qAsConst(m_packages)) {
        if (info.name == name) {
            ui->textBrowser->setText(LoadChangelog(info));
            // qDebug().noquote() << "Double Clicked row:" << index.row() << "Package:" << info.name;
            return;
        }
    }
}
